﻿using System;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Routing;

namespace MvcSimplyCleverPart3
{
    public class LocalizableRoute : Route
    {
        public LocalizableRoute(string url, IRouteHandler routeHandler)
            : base(url, routeHandler)
        {
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            if (values.ContainsKey("controller"))
            {
                string fallbackControllerName = values["controller"].ToString();
                string localizedControllerName = this.GetLocalizedControllerName(fallbackControllerName);

                if (!String.IsNullOrEmpty(localizedControllerName))
                {
                    values["Controller"] = this.GetLocalizedControllerName(fallbackControllerName);

                    if (values.ContainsKey("action"))
                    {
                        string fallbackActionName = values["action"].ToString();
                        values["action"] = this.GetLocalizedActionName(fallbackControllerName, fallbackActionName);
                    }
                }
            }

            return base.GetVirtualPath(requestContext, values);
        }

        private object GetLocalizedActionName(string fallbackControllerName, string fallbackActionName)
        {
            Type controllerType = (from t in Assembly.GetExecutingAssembly().GetTypes()
                                   where t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
                                   TypeUtilities.InheritsFrom(t, typeof(Controller)) &&
                                   t.Name == fallbackControllerName + "Controller"
                                   select t).FirstOrDefault();

            if (controllerType == null)
            {
                // There is no controller that matches this fallback name.
                return null;
            }

            // TODO: Find a better approach to matching ambiguous method names (i.e. one for GET
            // and one for POST) than simply picking the first match.
            MethodInfo methodInfo = (from mi in controllerType.GetMethods()
                                     where mi.Name == fallbackActionName
                                     select mi).FirstOrDefault();
            if (methodInfo == null)
            {
                return null;
            }

            return GetLocalizedActionName(methodInfo, fallbackActionName);
        }

        private string GetLocalizedActionName(MethodInfo methodInfo, string fallbackActionName)
        {
            object[] localizableActionNameAttributes = methodInfo.GetCustomAttributes(typeof(LocalizableActionNameAttribute), true);
            if (localizableActionNameAttributes.GetLength(0) == 0)
            {
                // this method does not have a localizable action name attribute
                return fallbackActionName;
            }

            LocalizableActionNameAttribute localizableActionNameAttribute = (LocalizableActionNameAttribute)localizableActionNameAttributes[0];
            return localizableActionNameAttribute.LocalizedActionName;
        }

        private string GetLocalizedControllerName(string fallbackControllerName)
        {
            Type controllerType = (from t in Assembly.GetExecutingAssembly().GetTypes()
                                   where t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
                                   TypeUtilities.InheritsFrom(t, typeof(Controller)) &&
                                   t.Name == fallbackControllerName + "Controller"
                                   select t).FirstOrDefault();

            if (controllerType == null)
            {
                // There is no controller that matches this fallback name.
                // Proceed as normal using the original controller name.
                return fallbackControllerName;
            }

            return GetLocalizedControllerName(controllerType, fallbackControllerName);
        }

        private string GetLocalizedControllerName(Type controllerType, string fallbackControllerName)
        {
            object[] localizableControllerNameAttributes = controllerType.GetCustomAttributes(typeof(LocalizableControllerNameAttribute), true);
            if (localizableControllerNameAttributes.GetLength(0) == 0)
            {
                // this type does not have a localizable controller name attribute
                return fallbackControllerName;
            }

            LocalizableControllerNameAttribute localizableControllerNameAttribute = (LocalizableControllerNameAttribute)localizableControllerNameAttributes[0];
            return localizableControllerNameAttribute.LocalizedControllerName;
        }
    }
}